package org.maxgroundstation.simulation.service;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class TCPClientSession implements Runnable {

	// Data members
	private ServiceCommunication comm;
	private Socket socket;
	private String clientID;
	private Thread sessionThread;
	private BufferedInputStream in;
	private DataOutputStream out;
	private Map<String, ServiceDataFrame> dataFrames;
	
	TCPClientSession(ServiceCommunication comm, Socket socket) {
		this.comm = comm;
		this.socket = socket;
		clientID = null;
		dataFrames = new HashMap<String, ServiceDataFrame>();
		
		try {
			in = new BufferedInputStream(socket.getInputStream());
			out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
		}
		catch (IOException e) {
			e.printStackTrace(System.err);
		}
		
		start();
	}
	
	String getClientID() {
		return clientID;
	}
	
	private void start() {
		if ((sessionThread == null) || !sessionThread.isAlive()) {
			sessionThread = new Thread(this, "tcp client session thread");
			sessionThread.start();
        }
	}
	
	@Override
	public void run() {
		while (!socket.isClosed()) {
			try {
				// Load fragment header data.
				byte[] headerData = new byte[40];
				
				int read = 0;
				while ((read < headerData.length) && !socket.isClosed()) {
					int newread = 0;
					try {
						newread = in.read(headerData, read, (headerData.length - read));
						//System.out.println("Reading " + newread + " bytes from TCP receiver input stream");
					}
					catch (Exception e) {
						//comm.log("Service receiver TCP header error: " + e.getMessage());
						close();
						break;
					}
					if (newread == -1) {
						close();
						break;
					}
					read += newread;
				}
				
				if (socket.isClosed()) continue;
				
				//comm.log("Receiving TCP header");
				
				// Create data fragment.
				ServiceDataFragment dataFrag = new ServiceDataFragment();
				dataFrag.setHeader(ServiceUtil.parseDataFragmentHeader(headerData));
				
				// Load fragment data.
				long frameLength = dataFrag.getHeader().getFrameLength();
				int fragSize = dataFrag.getHeader().getFragSize();
				int fragNumber = dataFrag.getHeader().getFragNumber();
				int totalFragNum = (int) Math.ceil((double) frameLength / (double) fragSize);
				int imageDataLength = fragSize;
                if (fragNumber == (totalFragNum - 1)) {
                	long totalFragLength = ((long) totalFragNum * (long) fragSize); 
                	if (totalFragLength > frameLength) {
                		long lesserFragLength = ((long) (totalFragNum - 1) * (long) fragSize);
                		imageDataLength = (int) (frameLength - lesserFragLength);
                	}
                }
				byte[] fragmentData = new byte[imageDataLength];
				read = 0;
				while (read < fragmentData.length) {
					int newread = 0;
					try {
						newread = in.read(fragmentData, read, (fragmentData.length - read));
					}
					catch (Exception e) {
						comm.log("Service receiver TCP fragment error: " + e.getMessage());
					};
					if (newread == -1) throw new IOException("End Of Stream");
					read += newread;
				}
				dataFrag.setData(fragmentData);
				
				// Create new client ID or update existing one.
				clientID = dataFrag.getHeader().getClientID();
				if (clientID.trim().isEmpty()) {
					clientID = comm.getClientManager().createNewClient();
					dataFrag.getHeader().setClientID(clientID);
				}
				else comm.getClientManager().updateClient(clientID);
				
				String serviceID = dataFrag.getHeader().getServiceID();
        		
        		//System.out.println("Receiving TCP packet size: " + (dataFrag.getData().length + 40));
        		
        		ServiceDataFrame dataFrame = dataFrames.get(serviceID);
        		if (dataFrame == null) {
        			comm.log("Creating new data frame for service ID: " + serviceID.trim());
        			dataFrame = new ServiceDataFrame();
        			dataFrames.put(serviceID, dataFrame);
        		}
        		
        		//System.out.println("Adding data fragment for service ID: " + serviceID.trim());
        		dataFrame.addDataFragment(dataFrag);
        			
        		if (dataFrame.isDataComplete()) {
        			//comm.log("Receiving full data: '" + dataFrame.getDataString() + "'");
        			try {
        				comm.handleResponseData(dataFrame, ServiceCommunication.COMM_TCP);
        			}
        			catch (Exception e) {
        				e.printStackTrace(System.err);
        			}
            		
            		dataFrames.remove(serviceID);
        		}
			}
			catch (IOException e) {
				e.printStackTrace(System.err);
			}
		}
	}
	
    /**
     * Sending a service post.
     * @param serviceID the service ID.
     * @param data the post data.
     * @throws Exception if error sending service post.
     */
    void sendServicePost(String serviceID, byte[] data) throws Exception {
    	ServiceDataFrame dataFrame = ServiceUtil.createDataFrame(serviceID, clientID, data);
    	
    	if (socket.isConnected()) {
    		//comm.log("Sending TCP Service Post");
            Iterator<ServiceDataFragment> i = dataFrame.getDataFragments().iterator();
            while (i.hasNext()) {
            	byte[] packetData = i.next().toPacketData();
               	out.write(packetData);
               	out.flush();
            }
        }
    }
    
    void close() throws IOException {
    	in.close();
    	out.close();
    	socket.close();
    }
    
    boolean isClosed() {
    	return socket.isClosed();
    }
}